/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { readFile, writeFile } from 'node:fs/promises';
import { generateSettingsSchema } from './generate-settings-schema.js';
import {
  escapeBackticks,
  formatDefaultValue,
  formatWithPrettier,
  injectBetweenMarkers,
  normalizeForCompare,
} from './utils/autogen.js';

import type {
  SettingDefinition,
  SettingsSchema,
  SettingsSchemaType,
} from '../packages/cli/src/config/settingsSchema.js';

const START_MARKER = '<!-- SETTINGS-AUTOGEN:START -->';
const END_MARKER = '<!-- SETTINGS-AUTOGEN:END -->';

const MANUAL_TOP_LEVEL = new Set(['mcpServers', 'telemetry', 'extensions']);

interface DocEntry {
  path: string;
  type: string;
  description: string;
  defaultValue: string;
  requiresRestart: boolean;
  enumValues?: string[];
}

export async function main(argv = process.argv.slice(2)) {
  const checkOnly = argv.includes('--check');

  await generateSettingsSchema({ checkOnly });

  const repoRoot = path.resolve(
    path.dirname(fileURLToPath(import.meta.url)),
    '..',
  );
  const docPath = path.join(repoRoot, 'docs/get-started/configuration.md');

  const { getSettingsSchema } = await loadSettingsSchemaModule();
  const schema = getSettingsSchema();
  const sections = collectEntries(schema);
  const generatedBlock = renderSections(sections);

  const doc = await readFile(docPath, 'utf8');
  const injectedDoc = injectBetweenMarkers({
    document: doc,
    startMarker: START_MARKER,
    endMarker: END_MARKER,
    newContent: generatedBlock,
    paddingBefore: '\n',
    paddingAfter: '\n',
  });
  const formattedDoc = await formatWithPrettier(injectedDoc, docPath);

  if (normalizeForCompare(doc) === normalizeForCompare(formattedDoc)) {
    if (!checkOnly) {
      console.log('Settings documentation already up to date.');
    }
    return;
  }

  if (checkOnly) {
    console.error(
      'Settings documentation is out of date. Run `npm run docs:settings` to regenerate.',
    );
    process.exitCode = 1;
    return;
  }

  await writeFile(docPath, formattedDoc);
  console.log('Settings documentation regenerated.');
}

async function loadSettingsSchemaModule() {
  const modulePath = '../packages/cli/src/config/settingsSchema.ts';
  return import(modulePath);
}

function collectEntries(schema: SettingsSchemaType) {
  const sections = new Map<string, DocEntry[]>();

  const visit = (
    current: SettingsSchema,
    pathSegments: string[],
    topLevel?: string,
  ) => {
    for (const [key, definition] of Object.entries(current)) {
      if (pathSegments.length === 0 && MANUAL_TOP_LEVEL.has(key)) {
        continue;
      }

      const newPathSegments = [...pathSegments, key];
      const sectionKey = topLevel ?? key;
      const hasChildren =
        definition.type === 'object' &&
        definition.properties &&
        Object.keys(definition.properties).length > 0;

      if (!hasChildren) {
        if (!sections.has(sectionKey)) {
          sections.set(sectionKey, []);
        }

        sections.get(sectionKey)!.push({
          path: newPathSegments.join('.'),
          type: formatType(definition),
          description: formatDescription(definition),
          defaultValue: formatDefaultValue(definition.default, {
            quoteStrings: true,
          }),
          requiresRestart: Boolean(definition.requiresRestart),
          enumValues: definition.options?.map((option) =>
            formatDefaultValue(option.value, { quoteStrings: true }),
          ),
        });
      }

      if (hasChildren && definition.properties) {
        visit(definition.properties, newPathSegments, sectionKey);
      }
    }
  };

  visit(schema, []);
  return sections;
}

function formatDescription(definition: SettingDefinition) {
  if (definition.description?.trim()) {
    return definition.description.trim();
  }
  return 'Description not provided.';
}

function formatType(definition: SettingDefinition): string {
  switch (definition.ref) {
    case 'StringOrStringArray':
      return 'string | string[]';
    case 'BooleanOrString':
      return 'boolean | string';
    default:
      return definition.type;
  }
}

function renderSections(sections: Map<string, DocEntry[]>) {
  const lines: string[] = [];

  for (const [section, entries] of sections) {
    if (entries.length === 0) {
      continue;
    }

    lines.push(`#### \`${section}\``);
    lines.push('');

    for (const entry of entries) {
      lines.push(`- **\`${entry.path}\`** (${entry.type}):`);
      lines.push(`  - **Description:** ${entry.description}`);
      lines.push(`  - **Default:** \`${escapeBackticks(entry.defaultValue)}\``);

      if (entry.enumValues && entry.enumValues.length > 0) {
        const values = entry.enumValues
          .map((value) => `\`${escapeBackticks(value)}\``)
          .join(', ');
        lines.push(`  - **Values:** ${values}`);
      }

      if (entry.requiresRestart) {
        lines.push('  - **Requires restart:** Yes');
      }

      lines.push('');
    }
  }

  return lines.join('\n').trimEnd();
}

if (process.argv[1]) {
  const entryUrl = pathToFileURL(path.resolve(process.argv[1])).href;
  if (entryUrl === import.meta.url) {
    await main();
  }
}
